package DB;

import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;

import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;

import Domain.stompProtocol.ForumFrame;
import Logger.ForumLogger;

import objects.Frm;
import objects.Message;
import objects.Message.MessagePK;
import objects.Trd;
import objects.User;
import objects.User.Type;



public class DataBase
{
	public static final String DATE_FORMAT_NOW = "yyyy-MM-dd  HH:mm:ss";
	private Calendar _cal; 
	private SessionFactory _factory;

	public DataBase()
	{

		AnnotationConfiguration config = new AnnotationConfiguration();
		config.addAnnotatedClass(UserFriends.class);
		config.addAnnotatedClass(Trd.class);
		config.addAnnotatedClass(Frm.class);
		config.addAnnotatedClass(Message.class);
		config.addAnnotatedClass(User.class);
		config.configure("hibernate.cfg.xml");
		_factory = config.buildSessionFactory();

		//creating new schema
		new SchemaExport(config).create(true,true);
		//first true for printing commands to log file, second to excute the commands for database

		this.addUser("manager",Type.MANAGER);

		this.addForum("Sport");
		this.addThreadToFroum("Sport", "Soccer");
		User user = this.findUser("manager");
		Frm forum = this.findFroum("Sport");
		Trd thread = this.getThread(forum, "Soccer");
		this.addMessage(thread, "Last night game", user, "Bad game");

	}

	public void addUser(String userName, Enum<Type> type){
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		User user = new User();
		user.setUserName(userName);
		user.setActive(true);
		user.setType(type);
		session.save(user);
		session.getTransaction().commit();
		ForumLogger.write(Level.INFO,"adding new user: "+userName+ " of type: "+type+" to the DB");
	}

	public void addUser(User user){
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		user.setActive(true);
		session.save(user);
		session.getTransaction().commit();
		ForumLogger.write(Level.INFO,"adding new user: "+user+ "to the DB");
	}

	public User findUser(String userName){
		//public UserTuple getUser(String userName){
		User user = null;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from User where userName = :name"); 
		//in hibernate the query must have the name of the class after from and not the name of the table!
		query.setString("name", userName);
		List userList = query.list();
		if (!userList.isEmpty()){
			user = (User)userList.get(0);
			ForumLogger.write(Level.INFO,"found user: "+userName+ "in the DB");
		}
		else {
			ForumLogger.write(Level.INFO,"could not found the user: "+userName+ "in the DB");
		}
		
		session.getTransaction().commit();
		return user;
	}

	public Frm addForum(String forumName){
		try{
			Session session = _factory.getCurrentSession();
			session.beginTransaction();
			Frm forum = new Frm();
			forum.setForumName(forumName);
			session.save(forum);
			session.getTransaction().commit();
			ForumLogger.write(Level.INFO,"adding the forum: "+forumName+" to the DB");
			return forum;
		}
		catch (org.hibernate.exception.ConstraintViolationException e) {
			ForumLogger.write(Level.INFO,"couldn't add the forum: "+forumName+" to the DB, got hibernate Exception");
			return null;
		}
	}


	//public ForumTuple getForum(String forumName)
	public Frm findFroum(String forumName)
	{
		Frm forum = null;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Frm where forumName = :name");
		query.setString("name", forumName);
		List userList = query.list();
		if (userList.isEmpty()){
			ForumLogger.write(Level.INFO,"could not find the forum: "+forumName+" in the DB");
			return null; //forum not found
		}
		ForumLogger.write(Level.INFO,"found the forum: "+forumName+" in the DB");
		forum = (Frm)userList.get(0);
		session.getTransaction().commit();
		return forum;
	}

	public Trd addThreadToFroum(String nameOfForum ,String nameOfThread){
		//public void addThread(ForumTuple forumName, String threadName){
		Trd trd = null;
		try{
			Frm forum = this.findFroum(nameOfForum);
			Session session = _factory.getCurrentSession();
			session.beginTransaction();
			if (forum == null){
				ForumLogger.write(Level.INFO,"could not add thread: "+nameOfThread+" to the forum: "+nameOfForum+
						" in the DB, forum does nor exist");
				return null;
			}
			trd = new Trd();
			trd.setForum(forum);
			trd.setThreadName(nameOfThread);
			session.save(trd);
			session.getTransaction().commit();
		}
		catch (org.hibernate.exception.ConstraintViolationException e) {//if PK already exist
			e.printStackTrace();
			ForumLogger.write(Level.INFO,"could not add thread: "+nameOfThread+" to the forum: "+nameOfForum+
			" in the DB, trd already exist in the forum");
			return null;
		}
		ForumLogger.write(Level.INFO,"added thread: "+nameOfThread+" to the forum: "+nameOfForum+
				" in the DB");
		return trd;
	}

	public Trd getThread(Frm forum, String threadName)
	{
		Trd trd = new Trd();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Trd where forum_forumName = :forum and threadName = :thread");
		query.setString("thread", threadName);
		query.setString("forum", forum.getForumName());
		List userList = query.list();
		if (!userList.isEmpty()){
			ForumLogger.write(Level.INFO,"getting the thread: "+threadName+" from the forum: "+forum+
			" in the DB");
			trd = (Trd)userList.get(0);
		}
		else{
			ForumLogger.write(Level.INFO,"could not find thread: "+threadName+" in the forum: "+forum+
			" in the DB, forum or thread does nor exist");
		}
		session.getTransaction().commit();
		return trd;
	}

	public void addMessage(Trd thread, String title, User user, String msgStr){
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Message msg = new Message();
		//msg.setForum(thread.getForum());
		msg.setThread(thread);
		msg.setTitle(title);
		msg.setOwner(user);
		_cal = Calendar.getInstance();
		msg.setDate(_cal.getTime());
		msg.setMessage(msgStr);
		session.save(msg);
		session.getTransaction().commit();
	}

	public Message getMessage(Trd thread, String title)
	{
		Message msg = null;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		MessagePK mPK = new MessagePK();
		mPK.setThread(thread);
		mPK.setTitle(title);
		ForumLogger.write(Level.INFO,"getting message: "+title+" to the thread: "+thread.getThreadName()+
		" in the DB");
		return (Message)session.get(Message.class,mPK);
	
	}

	public void addFriend(User user, User friend){
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		UserFriends userFriends = new UserFriends();
		userFriends.setUser(user);
		userFriends.setFriend(friend);
		session.save(userFriends);
		session.getTransaction().commit();
		ForumLogger.write(Level.INFO,"adding the friend: "+friend.getUserName()+" to the user: "+user.getUserName()+
		" in the DB");
	}

	/*	public UserFriends getFriends(UserTuple user)
	{
		MessageTuple msg = null;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from MessageTuple where thread_forum_forumName = :fourmName " +
		"and thread_threadName = :threadName and title = :title");
		query.setString("title", title);
		query.setString("fourmName", thread.getForum().getForumName());
		query.setString("threadName", thread.getThreadName());
		List userList = query.list();
		if (!userList.isEmpty()){
			msg = (MessageTuple)userList.get(0);
		}
		session.getTransaction().commit();
		return msg;
	}*/

	public boolean updateObject(Object obj){
		boolean ans = true;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		try{
			session.update(obj);
		}catch(HibernateException e){
			e.printStackTrace();
			ans = false;
		}
		session.getTransaction().commit();
		return ans;
	}

	/**
	 * deleting a user will make the constraint in messages to fail
	 * (there will be no user to connect the message to), so only the user status is changed!
	 * @param obj
	 * @return success or not...   
	 */
	public boolean deleteObject(Object obj){
		boolean ans = true;
		if (obj.getClass().equals(User.class)){
			String name= ((User)obj).getUserName();
			((User)obj).setActive(false);
			updateObject(obj);
			System.out.println("user "+name+" no longer active");
		}
		else{
			Session session = _factory.getCurrentSession();
			session.beginTransaction();
			try{
				session.delete(obj);
			}catch(HibernateException e){
				e.printStackTrace();
				ans = false;
			}
			session.getTransaction().commit();
		}
		return ans;
	}

	public User findOwnerOfMessage(String nameOfForum ,String nameOfThread,String title)
	{
		User user = null;
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Message where " +
				"thread_forum_forumName = :fourmName and thread_threadName = :threadName " +
		"and title = :title");
		//query.setString("title", title);
		query.setString("fourmName", nameOfForum);
		query.setString("threadName", nameOfThread);
		query.setString("title", title);
		List userList = query.list();
		if (!userList.isEmpty()){
			Message message = (Message)userList.get(0);
			user = message.getOwner();
			ForumLogger.write(Level.INFO,"getting the owner: "+user.getUserName()+" of the message: "+title+
			" in thread: "+nameOfThread+" in forum: "+nameOfForum+" in the DB");
			//System.out.println(user.getUserName());
		}
		else{
			ForumLogger.write(Level.INFO,"could not get the owner of the message: "+title+
					" in thread: "+nameOfThread+" in forum: "+nameOfForum+" in the DB");
		}
		session.getTransaction().commit();
		return user;
	}
	/*
		ForumTuple f = this.findFroum(nameOfForum);
		if (f==null)
			return null;
		Vector <MessageTuple> msgs =f.getMessageFromThread(nameOfThread);
		if (msgs==null)
			return null;
		for (Iterator iterator = msgs.iterator(); iterator.hasNext();) {
			MessageTuple message = (MessageTuple) iterator.next();
			if(message.getTitle().trim().equals(msg.trim()))
			{
				return message.getOwner();
			}

		}
		return null;*/

	public Vector<Trd> getThreads(Frm f) {
		Vector<Trd> ans = new Vector<Trd>();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Trd where forum_forumName = :forum");
		query.setString("forum", f.getForumName());
		List trdList = query.list();
		if (!trdList.isEmpty()){
			for (Iterator<Trd> iterator = trdList.iterator(); iterator.hasNext();)
			{
				Trd trd = (Trd) iterator.next();
				ans.add(trd);
			}
			session.getTransaction().commit();
		}
		ForumLogger.write(Level.INFO,"getting the threads from the forum: "+f.getForumName()+
					" in the DB");
		return ans;
	}

	public Vector<Trd> getThreads(String fname) {
		Vector<Trd> ans = new Vector<Trd>();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Trd where forum_forumName = :forum");
		query.setString("forum", fname);
		List trdList = query.list();
		if (!trdList.isEmpty()){
			//ans = new Vector<Trd>();
			for (Iterator<Trd> iterator = trdList.iterator(); iterator.hasNext();)
			{
				Trd trd = (Trd) iterator.next();
				ans.add(trd);
			}
			session.getTransaction().commit();
		}
		ForumLogger.write(Level.INFO,"getting the threads from the forum: "+fname+
				" in the DB");
		return ans;
	}

	public Vector <Message> getMessageFromThread (String nameOfForum,String nameOfThread){

		Vector <Message> messages = new Vector<Message>();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		//Query query = session.createSQLQuery("select * from Messages where " +
		Query query = session.createQuery("from Message where " +
		"thread_forum_forumName = :fourmName and thread_threadName = :threadName order by date desc");
		//query.setString("title", title);
		query.setString("fourmName", nameOfForum);
		query.setString("threadName", nameOfThread);
		query.setFlushMode(FlushMode.COMMIT);   // Avoid recursive call and StackOverflowError
		System.out.println("enter");
		List messagesList = query.list();
		System.out.println("out");
		if (!messagesList.isEmpty()){
			messages = new Vector<Message>();
			for (Iterator<Message> iterator = messagesList.iterator(); iterator.hasNext();)
			{
				Message object = (Message) iterator.next();
				messages.add(object);
			}
			session.getTransaction().commit();
		}
		ForumLogger.write(Level.INFO,"getting the messages from the thread: "+nameOfThread+ " in the forum: "
				+nameOfForum+" in the DB");
		return messages;
	}



	public Vector<Frm> getForums(){
		Vector<Frm> ans = new Vector<Frm>();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from Frm");
		List<Frm> userList = (List<Frm>)query.list();
		session.getTransaction().commit();
		if (!userList.isEmpty()){
			//ans = new Vector<Frm>();
			for (Iterator<Frm> iterator = userList.iterator(); iterator.hasNext();)
			{

				Frm frm = (Frm) iterator.next();
				ans.add(frm);
			}

		}
		ForumLogger.write(Level.INFO,"getting the forums in the DB");
		return ans;

	}

	public void removeUser (User u){
		//deleting his friends and the connections between other users to him
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("delete from UserFriends where friendPK.user = :name" +
		" or friendPK.friend = :name"); 
		//in hibernate the query must have the name of the class after from and not the name of the table!
		query.setString("name", u.getUserName());
		query.executeUpdate();

		//settings the user status to not active (we need him for messages that he published)
		this.deleteObject(u);
		ForumLogger.write(Level.INFO,"removing the user: "+u.getUserName()+
		" from the DB");
	}

	public  Vector<User> getUsers(){
		Vector<User> ans = new Vector<User>();
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("from User where isActive = :true"); 
		//in hibernate the query must have the name of the class after from and not the name of the table!
		//query.setString("active", User.Status.ACTIVE.toString());
		query.setBoolean("true", true);
		List userList = query.list();
		if (!userList.isEmpty()){
			//ans = new Vector<User>();
			for (Iterator<User> iterator = userList.iterator(); iterator.hasNext();)
			{
				User user = (User) iterator.next();
				ans.add(user);
			}
			session.getTransaction().commit();
		}
		ForumLogger.write(Level.INFO,"getting the users from the DB");
		return ans;
	}

	public Vector<User> getFriendsOfUser(String nameOfUser)
	{
		User user = findUser(nameOfUser);
		Vector<User> friendVec = new Vector<User>(0,1); 
		if(user != null){
			Session session = _factory.getCurrentSession();
			session.beginTransaction();
			Query query = session.createQuery("select friendPK.friend  from UserFriends where user_userName = :user");
			query.setString("user", user.getUserName());
			List<User> friendList = (List<User>)query.list();
			session.getTransaction().commit();
			if(friendList != null){
				for (int i = 0; i < friendList.size(); i++)
				{
					friendVec.add(friendList.get(i));
				}
			}
		}
		ForumLogger.write(Level.INFO,"getting freinds of user: "+nameOfUser+" from the DB");
		return friendVec;
	}

	/**
	 * 
	 * @param nameOfThread
	 * @param TitleOfMessage
	 * @param NewContectOfMessage
	 * @return -2 if thread doesn't found , return -1 if msg doesn't found ,
	 *  -6 if user not manager or owner,return 1 on seccss 
	 */
	public int editMessageInThread(Frm frm, User u, String nameOfThread, String titleOfMessage, String newContectOfMessage)
	{
		Trd t = getThread(frm,nameOfThread);
		if (t == null)
			return -2;
		Vector <Message> m = getMessageFromThread(frm.getForumName(),t.getThreadName());
		for (Iterator iterator = m.iterator(); iterator.hasNext();)
		{
			Message message = (Message) iterator.next();
			if (message.getTitle().trim().equals(titleOfMessage.trim()))
			{
				//System.err.println(u.getUserName());
				//System.err.println(message.getOwner());
				if (message.getOwner().getUserName().equals(u.getUserName())||u.getType().equals(User.Type.MANAGER))
				{
					_cal = Calendar.getInstance();
					message.setDate(_cal.getTime());
					message.setMessage(newContectOfMessage);
					updateObject(message);
					ForumLogger.write(Level.INFO,"edit message: "+titleOfMessage+" in thread: "+nameOfThread+
					" by the user: "+u.getUserName()+" in the DB");
					return 1;
				}
				else return -6;
			}
		}
		return -1;
	}

	/**
	 * 
	 * @param nameOfThread
	 * @param msg
	 * @return -2 if thread doesn't found , return -1 if message doesn't found ,/
	 * return 1 if object remove
	 */
	public int removeMessageFromThread (Frm f,String nameOfThread,String title)
	{
		Trd t = getThread(f, nameOfThread);
		if (t == null){
			ForumLogger.write(Level.INFO,"could not remove message: "+title+ " in thread: "+nameOfThread+
						" forum: "+f.getForumName()+" in the DB, thread not found");
			return ForumFrame.ERRThreadNotFound;
		}
		Message msg = getMessage(t, title);
		if (msg != null)
		{
			deleteObject(msg);
			ForumLogger.write(Level.INFO,"removed message: "+title+ " in thread: "+nameOfThread+
					" forum: "+f.getForumName()+" from the DB");
			return 1;
		}
		else{
			ForumLogger.write(Level.INFO,"could not remove message: "+title+ " in thread: "+nameOfThread+
					" forum: "+f.getForumName()+" in the DB, message not found");
			return ForumFrame.ERRMsseageNotFound;
		}

	}

	public void removeFreind(String user, String friendToBeRemoved)
	{

		//deleting his friends and the connections between other users to him
		Session session = _factory.getCurrentSession();
		session.beginTransaction();
		Query query = session.createQuery("delete from UserFriends where friendPK.user = :name" +
		" and friendPK.friend = :friend"); 
		//in hibernate the query must have the name of the class after from and not the name of the table!
		query.setString("name", user);
		query.setString("friend",friendToBeRemoved);
		query.executeUpdate();
		ForumLogger.write(Level.INFO,"removing the freind: "+friendToBeRemoved+ " from user: "+user +" from DB");

	}

}
